package com.circle.interests.biz.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.circle.common.core.enums.Switch;
import com.circle.common.core.exception.RRException;
import com.circle.interests.biz.constants.Constants;
import com.circle.interests.biz.core.category.honglian.PrizeHongLianHandler;
import com.circle.interests.biz.core.category.wxredpackage.PrizeWxRedPackageHandler;
import com.circle.interests.biz.core.protocol.RechargeResult;
import com.circle.interests.biz.dto.PrizeGameWinRecordTotalDto;
import com.circle.interests.biz.entity.PrizeGameInventory;
import com.circle.interests.biz.entity.PrizeGameWinRecord;
import com.circle.interests.biz.enums.PrizeStatusEnum;
import com.circle.interests.biz.mapper.PrizeGameWinRecordMapper;
import com.circle.interests.biz.service.IPrizeGameInventoryService;
import com.circle.interests.biz.service.IPrizeGameWinRecordService;
import com.circle.prize.library.api.dto.game.PrizeGameWinResultDto;
import com.circle.prize.library.api.dto.game.PrizeGameWriteOffResultDto;
import com.circle.prize.library.api.enums.InventoryTypeEnum;
import com.circle.prize.library.api.enums.PrizeTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 中奖记录 服务实现类
 * </p>
 *
 * @author generator
 * @since 2021-12-17
 */
@Slf4j
@Service
@Transactional
public class PrizeGameWinRecordServiceImpl extends ServiceImpl<PrizeGameWinRecordMapper, PrizeGameWinRecord> implements IPrizeGameWinRecordService {

    @Resource
    private IPrizeGameInventoryService prizeGameInventoryService;
    @Resource
    private PrizeWxRedPackageHandler wxRedPackageHandler;
    @Resource
    private PrizeHongLianHandler hongLianHandler;
    @Resource
    private RedissonClient redissonClient;

    @Override
    public PrizeGameWinResultDto winPrize(Integer sceneType, String sceneId, String openId, String prizeCode, String code, Integer amount) {
        PrizeGameWinResultDto resultDto = new PrizeGameWinResultDto();
        resultDto.setOpenId(openId);
        // 开始校验并扣除库存
        PrizeGameInventory prizeGameInventory = prizeGameInventoryService.get(sceneType, sceneId, prizeCode);
        log.info("用户{}中奖，活动{}-{}-{}的库存开始扣减金额:{}", openId, sceneType, sceneId, prizeCode, amount);
        boolean success = decrement(prizeGameInventory, 1, amount);
        if (!success) {
            log.info("用户{}中奖，活动{}-{}-{}的库存不足", openId, sceneType, sceneId, prizeCode);
            resultDto.setWinFlag(Switch.OFF.getCode());
            resultDto.setMessage("库存不足");
            return resultDto;
        }
        try {
            PrizeGameWinRecord record = new PrizeGameWinRecord();
            record.setSceneType(sceneType);
            record.setSceneId(sceneId);
            record.setCode(code);
            record.setPrizeCode(prizeGameInventory.getPrizeCode());
            record.setPrizeType(prizeGameInventory.getPrizeType());
            record.setAmount(amount);
            record.setProductCode(prizeGameInventory.getProductCode());
            record.setOpenId(openId);
            record.setWriteOffStatus(1);
            save(record);
            resultDto.setWinFlag(Switch.ON.getCode());
        } catch (Exception e) {
            log.error("保存中奖信息失败,开始回滚库存", e);
            boolean rollbackFlag = decrement(prizeGameInventory, -1, amount);
            log.info("库存回滚结果:{}", rollbackFlag);
        }
        return resultDto;
    }

    private PrizeGameWriteOffResultDto writeOff(Integer sceneType, String sceneId, String openId, String nickname, String payAppId, String payOpenId, String phoneNumber, String orderCode, String remark) {
        PrizeGameWriteOffResultDto resultDto = new PrizeGameWriteOffResultDto();
        PrizeGameWinRecord record = getByCode(orderCode);
        if (record == null) {
            resultDto.setWriteOffStatus(4);
            resultDto.setMessage("中奖记录未找到");
            return resultDto;
        } else if (record.getWriteOffStatus() != 1 && record.getWriteOffStatus() != 4) {
            resultDto.setWriteOffStatus(4);
            resultDto.setMessage("中奖已核销");
            return resultDto;
        }
        RechargeResult result;
        if (Objects.equals(record.getPrizeType(), PrizeTypeEnum.RED_PACKAGE.getCode())) {
            // 红包
            result = wxRedPackageHandler.recharge(sceneId, openId, nickname, phoneNumber, payAppId, payOpenId, record.getAmount(), remark);
        } else if (Objects.equals(record.getPrizeType(), PrizeTypeEnum.HONG_LIAN.getCode())) {
            // 鸿联商品
            result = hongLianHandler.recharge(record.getCode(), phoneNumber, record.getProductCode(), remark);
            resultDto.setUseType(result.getUseType());
        } else {
            result = new RechargeResult();
            result.setPrizeStatus(PrizeStatusEnum.WRITE_OFF_FAIL.getCode());
        }
        if (PrizeStatusEnum.WRITE_OFF_SUCCESS.getCode().equals(result.getPrizeStatus())) {
            record.setWriteOffStatus(3);
            record.setRemark("");
        } else if (PrizeStatusEnum.WRITE_OFF_FAIL.getCode().equals(result.getPrizeStatus())) {
            record.setWriteOffStatus(4);
            record.setRemark(result.getStatusInfo());
        } else {
            record.setWriteOffStatus(2);
        }
        record.setCardNumber(result.getCardNo());
        record.setCardPassword(result.getCardPassword());
        record.setOrderCode(result.getOrderCode());
        updateById(record);
        resultDto.setCardNumber(record.getCardNumber());
        resultDto.setCardPassword(record.getCardPassword());
        resultDto.setWriteOffStatus(record.getWriteOffStatus());
        return resultDto;
    }

    @Override
    public PrizeGameWriteOffResultDto winAndWriteOffPrize(Integer sceneType, String sceneId, String openId, String nickname, String payAppId, String payOpenId,
                                                          String phoneNumber, String prizeCode, String code, Integer amount, String remark) {
        PrizeGameWriteOffResultDto writeOffResultDto = new PrizeGameWriteOffResultDto();
        PrizeGameWinResultDto winResultDto = winPrize(sceneType, sceneId, openId, prizeCode, code, amount);
        writeOffResultDto.setWinFlag(winResultDto.getWinFlag());
        if (Switch.ON.getCode().equals(winResultDto.getWinFlag())) {
            writeOffResultDto = writeOff(sceneType, sceneId, openId, nickname, payAppId, payOpenId, phoneNumber, code, remark);
        } else {
            // 未中奖信息
            writeOffResultDto.setMessage(winResultDto.getMessage());
        }
        return writeOffResultDto;
    }

    @Override
    public PrizeGameWriteOffResultDto writeOff(Integer sceneType, String sceneId, String openId, String phoneNumber, String orderCode, String remark) {
        return writeOff(sceneType, sceneId, openId, null, null, null, phoneNumber, orderCode, remark);
    }

    @Override
    public PrizeGameWinRecordTotalDto count(Integer sceneType, String sceneId, String prizeCode) {
        return baseMapper.count(sceneType, sceneId, prizeCode);
    }

    @Override
    public PrizeGameWinRecord getByOrderCode(String orderCode) {
        return getOne(Wrappers.lambdaQuery(PrizeGameWinRecord.class).eq(PrizeGameWinRecord::getOrderCode, orderCode));
    }

    private PrizeGameWinRecord getByCode(String code) {
        return getOne(Wrappers.lambdaQuery(PrizeGameWinRecord.class).eq(PrizeGameWinRecord::getCode, code));
    }

    private boolean decrement(PrizeGameInventory inventory, Integer count, Integer amount) {
        Assert.notNull(count, "奖品数量不能为空");
        InventoryTypeEnum inventoryTypeEnum = InventoryTypeEnum.getByCode(inventory.getInventoryType());
        int realCount;
        switch (inventoryTypeEnum) {
            case UNLIMITED:
                log.info("活动{}-{}-{}库存不限量", inventory.getSceneType(), inventory.getSceneId(), inventory.getPrizeCode());
                return true;
            case LIMITED_NUMBER:
                realCount = count;
                break;
            case LIMITED_AMOUNT:
                if (amount == null) {
                    throw new RRException("奖品金额不能为空");
                }
                realCount = count * amount;
                break;
            default:
                throw new RRException("库存类型错误");
        }
        // 校验并扣减库存
        RAtomicLong atomicLong = getSendCount(inventory.getSceneType(), inventory.getSceneId(), inventory.getPrizeCode(), inventoryTypeEnum);
        if (atomicLong.get() >= inventory.getTotal()) {
            log.info("取消发放{}-{}-{},已发放:{},达到上限:{}", inventory.getSceneType(), inventory.getSceneId(), inventory.getPrizeCode(), atomicLong.get(), inventory.getTotal());
            return false;
        }
        if (atomicLong.addAndGet(realCount) > inventory.getTotal()) {
            atomicLong.addAndGet(-realCount);
            log.info("取消发放{}-{}-{},已发放:{},再发:{}将超过上限:{}", inventory.getSceneType(), inventory.getSceneId(), inventory.getPrizeCode(), atomicLong.get(), realCount, inventory.getTotal());
            return false;
        }
        log.info("扣减活动{}-{}-{}的数量:{},总量:{},已发放总量:{}", inventory.getSceneType(), inventory.getSceneId(), inventory.getPrizeCode(), realCount, inventory.getTotal(), atomicLong.get());
        return true;
    }

    private RAtomicLong getSendCount(Integer sceneType, String sceneId, String prizeCode, InventoryTypeEnum inventoryTypeEnum) {
        String key = String.format(Constants.PRIZE_INVENTORY_ATOMIC_KEY, sceneType, sceneId, prizeCode);
        RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
        if (!atomicLong.isExists()) {
            RLock lock = redissonClient.getLock(key + ":lock");
            try {
                log.info("获取初始化库存锁开始:{}-{}-{}", sceneType, sceneId, prizeCode);
                boolean lockResult = lock.tryLock(60L, 60L, TimeUnit.SECONDS);
                if (!lockResult) {
                    throw new RRException("请稍后重试");
                }
                log.info("获取初始化库存锁成功:{}-{}-{}", sceneType, sceneId, prizeCode);
                if (atomicLong.isExists()) {
                    return atomicLong;
                }
                log.info("初始化库存量开始:{}-{}-{}", sceneType, sceneId, prizeCode);
                int inventoryTotal;
                PrizeGameWinRecordTotalDto totalDto = count(sceneType, sceneId, prizeCode);
                switch (inventoryTypeEnum) {
                    case UNLIMITED:
                    case LIMITED_NUMBER:
                        inventoryTotal = totalDto.getTotal();
                        break;
                    case LIMITED_AMOUNT:
                        inventoryTotal = totalDto.getAmountTotal();
                        break;
                    default:
                        throw new RRException("库存类型错误");
                }
                atomicLong.set(inventoryTotal);
                atomicLong.expire(1, TimeUnit.DAYS);
                log.info("初始化库存量结束:{}-{}-{}，库存量:{}", sceneType, sceneId, prizeCode, inventoryTotal);
            } catch (Exception e) {
                log.error("获取已发放奖品总数失败", e);
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        return atomicLong;
    }

}
